/*
 * Galaxium Messenger
 * 
 * Copyright (C) 2005-2007 Ben Motmans <ben.motmans@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Collections.Generic;

using Gdk;
using Gtk;
using Pango;

using Galaxium.Core;
using Galaxium.Client;
using Galaxium.Protocol;
using Anculus.Core;

namespace Galaxium.Gui.GtkGui
{
	public class TextMessageDisplay : AbstractMessageDisplay
	{
		internal struct EmoticonMark
		{
			public IEmoticon Emoticon;
			public TextChildAnchor Anchor;
			public Gtk.Image Image;
			public string Equivalent;
		}
		
		protected TextBuffer _textBuffer;
		internal TextTagTable _tagTable;
		protected TextMark _endMark;
		
		List<EmoticonMark> _emoticons = new List<EmoticonMark> ();
		
		protected string _lastIdentifier = String.Empty;
		
		protected TextTag _messageTag;
		protected TextTag _boldTag;
		protected TextTag _italicTag;
		protected TextTag _underlineTag;
		protected TextTag _strikeTag;
		
		protected Cursor _cursorHand;
		protected Cursor _cursorPointer;
		
		protected LinkTag _activeLink;
		protected bool _mouseOverLink;
		protected TextIter _mouseOverIter;
		
		protected TextView TextView
		{
			get { return Widget as TextView; }
		}
		
		protected TextBuffer TextBuffer
		{
			get { return _textBuffer; }
		}
		
		internal Gtk.Style Style
		{
			get { return TextView.Style; }
		}
		
		public TextMessageDisplay ()
			: base (new TextView (), true)
		{
			_cursorHand = new Cursor (CursorType.Hand2);
			_cursorPointer = new Cursor (CursorType.Xterm);
			
			TextView.Editable = false;
			TextView.DoubleBuffered = true;
			TextView.WrapMode = Gtk.WrapMode.Word;
			TextView.CursorVisible = false;
			TextView.LeftMargin = 2;
			TextView.RightMargin = 2;
			TextView.CanFocus = false;
			
			// Setup a normal message text tag.
			_messageTag = new TextTag("message_tag");
			_messageTag.LeftMargin = 50;
			
			TextTag nonMargineMessageTag = new TextTag("no_margin_message_tag");
			nonMargineMessageTag.LeftMargin = 5;
			
			// Setup a action message text tag.
			TextTag actionTag = new TextTag("action_tag");
			actionTag.Weight = Pango.Weight.Bold;
			actionTag.Scale = 0.8;
			actionTag.PixelsAboveLines = 5;
			actionTag.PixelsBelowLines = 5;
			
			TextTag historyTag = new TextTag("history_tag");
			historyTag.Weight = Pango.Weight.Light;
			historyTag.Foreground = "slate gray";
			historyTag.PixelsAboveLines = 5;
			
			// Setup a warning message text tag.
			TextTag warningTag = new TextTag("warning_tag");
			warningTag.Foreground = "red";
			warningTag.Weight = Pango.Weight.Bold;
			warningTag.Scale = 0.8;
			warningTag.PixelsAboveLines = 5;
			warningTag.PixelsBelowLines = 5;
			
			// Setup a time message text tag.
			TextTag timeTag = new TextTag("time_tag");
			timeTag.Scale = 0.9;
			timeTag.LeftMargin = 10;
			timeTag.Style = Pango.Style.Italic;
		
			// Setup a margin text tag.
			TextTag marginTag = new TextTag("margin_tag");
			marginTag.LeftMargin = 10;
			
			// Setup a bold message text tag.
			_boldTag = new TextTag("bold_tag");
			_boldTag.Weight = Pango.Weight.Ultrabold;
			
			// Setup a italic message text tag.
			_italicTag = new TextTag("italic_tag");
			_italicTag.Style = Pango.Style.Italic;
			
			// Setup a underline message text tag.
			_underlineTag = new TextTag("underline_tag");
			_underlineTag.Underline = Pango.Underline.Single;
			
			// Setup a strikethrough message text tag.
			_strikeTag = new TextTag("strike_tag");
			_strikeTag.Strikethrough = true; 
			
			// Add the above text tags to the table.
			_tagTable = new TextTagTable();
			_tagTable.Add(timeTag);
			_tagTable.Add(marginTag);
			_tagTable.Add(historyTag);
			_tagTable.Add(_messageTag);
			_tagTable.Add(nonMargineMessageTag);
			_tagTable.Add(actionTag);
			_tagTable.Add(warningTag);
			_tagTable.Add(_boldTag);
			_tagTable.Add(_italicTag);
			_tagTable.Add(_underlineTag);
			_tagTable.Add(_strikeTag);
			
			_textBuffer = new TextBuffer (_tagTable);
			TextView.Buffer = _textBuffer;

			TextView.ButtonPressEvent += TextViewButtonPress;
			TextView.ButtonReleaseEvent += TextViewButtonRelease;
			TextView.MotionNotifyEvent += TextViewMotionNotify;
			TextView.VisibilityNotifyEvent += TextViewVisibilityNotify;
		}
		
		public override void DisplayClear ()
		{
			_textBuffer.Clear ();
		}
		
		public override void DisplayMessage (IMessage msg)
		{
			if ((msg.Flags & MessageFlag.Message) == MessageFlag.Message)
			{
				if (!_lastIdentifier.Equals (msg.Source.UniqueIdentifier))
				{
					if ((msg.Flags & MessageFlag.Offline) == MessageFlag.Offline)
						WriteFromOffline (msg.Source);
					else
						WriteFrom (msg.Source);
					
					WriteNewLine ();
				}
				
				_lastIdentifier = msg.Source.UniqueIdentifier;
			}
			
			WriteMargin();
			WriteTime (msg.TimeStamp);
			
			WriteMessage (msg);
			
			WriteNewLine ();
			AutoScroll ();
		}
		
		public override void UpdateEmoticon (IEmoticon emot)
		{
			//emoticon has been received, we need to show it wherever it has been used
			
			for (int i = 0; i < _emoticons.Count; i++)
			{
				EmoticonMark mark = _emoticons[i];
				
				if (mark.Emoticon != emot)
					continue;
				
				Gtk.Image newImage = GtkUtility.LoadEmoticon (emot);
				
				if (newImage == null)
					continue;
				
				newImage.TooltipText = String.Format("{0} ({1})", emot.Name, mark.Equivalent);
				
				TextView.Remove (mark.Image);
				mark.Image = newImage;
				TextView.AddChildAtAnchor (mark.Image, mark.Anchor);
			}
		}
		
		protected virtual void WriteFromOffline (IEntity from)
		{
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.InsertWithTags(ref endIter,
				string.IsNullOrEmpty(from.DisplayIdentifier) ? "["+from.UniqueIdentifier+"]" : "["+from.DisplayIdentifier+"]",
				new EntityTag(this, from));
		}
		
		protected virtual void WriteFrom (IEntity from)
		{
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.InsertWithTags(ref endIter,
				from.DisplayIdentifier,
				new EntityTag(this, from));
		}
		
		protected virtual void WriteNewLine ()
		{
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.Insert(ref endIter, Environment.NewLine);
		}
		
		protected virtual void WriteUnformattedText (string text)
		{
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.Insert(ref endIter, text);
		}
		
		protected virtual void WriteTime (DateTime timestamp)
		{
			string format = String.Empty;
			
			//if (string.IsNullOrEmpty (format))
				format = timestamp.ToShortTimeString ();
			//else
				//format = timestamp.ToString (format);
			
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.InsertWithTagsByName(ref endIter, String.Concat ("(", format, ") "), "time_tag");
		}
		
		protected virtual void WriteMargin ()
		{
			TextIter endIter = _textBuffer.EndIter;
			_textBuffer.InsertWithTagsByName(ref endIter, "", "margin_tag");
		}
		
		protected virtual void WriteImage (byte[] data)
		{
			TextIter endIter = _textBuffer.EndIter;
			Gtk.TextChildAnchor anchor = _textBuffer.CreateChildAnchor (ref endIter);
			
			TextView.AddChildAtAnchor (GtkUtility.LoadImage (data), anchor);
		}
		
		protected virtual void WriteMessage (IMessage message)
		{
			TextMark firstMark = _textBuffer.CreateMark(null, _textBuffer.EndIter, true);
			
			foreach (IMessageChunk chunk in message.Chunks)
			{
				IMessageStyle style = chunk.Style;
				
				TextMark beginMark = _textBuffer.CreateMark (null, _textBuffer.EndIter, true);
				TextIter beginIter = _textBuffer.EndIter;
				
				string tagName = null;
				
				if (chunk is IEmoticonMessageChunk)
				{
					IEmoticon emot = (chunk as IEmoticonMessageChunk).Emoticon;
					
					EmoticonMark mark = new EmoticonMark();
					mark.Equivalent = (chunk as IEmoticonMessageChunk).Text;
					mark.Emoticon = emot;
					mark.Anchor = _textBuffer.CreateChildAnchor (ref beginIter);
					mark.Image = GtkUtility.LoadEmoticon (emot);
					
					if (mark.Image == null)
						continue;
					
					mark.Image.TooltipText = String.Format("{0} ({1})", emot.Name, (chunk as IEmoticonMessageChunk).Text);
					
					_emoticons.Add (mark);
					
					TextView.AddChildAtAnchor (mark.Image, mark.Anchor);
				}
				else if (chunk is ITextMessageChunk)
				{
					ITextMessageChunk tchunk = chunk as ITextMessageChunk;
					
					WriteUnformattedText (tchunk.Text);
					
					beginIter = _textBuffer.GetIterAtMark (beginMark);
					TextIter endIter = _textBuffer.EndIter;
					
					if (chunk is IURIMessageChunk)
						_textBuffer.ApplyTag (new LinkTag (_tagTable, (chunk as IURIMessageChunk).URI), beginIter, endIter);
					
					if ((message.Flags & MessageFlag.Message) != MessageFlag.Message)
					{
						tagName = "color_AA0000";
						Gtk.TextTag colorTag = _tagTable.Lookup (tagName);
						
						if (colorTag == null)
						{
							colorTag = new Gtk.TextTag (tagName);
							colorTag.ForegroundGdk = new Gdk.Color (0xAA, 0x00, 0x00);
							_tagTable.Add (colorTag);
						}
						
						_textBuffer.ApplyTag (colorTag, beginIter, endIter);
					}
					else if ((message.Flags & MessageFlag.History) == MessageFlag.History)
					{
						tagName = "color_888888";
						Gtk.TextTag colorTag = _tagTable.Lookup (tagName);
						
						if (colorTag == null)
						{
							colorTag = new Gtk.TextTag (tagName);
							colorTag.ForegroundGdk = new Gdk.Color (0x88, 0x88, 0x88);
							_tagTable.Add (colorTag);
						}
						
						_textBuffer.ApplyTag (colorTag, beginIter, endIter);
					}
					
					if (style == null)
						continue;
					
					if (style.Bold)
						_textBuffer.ApplyTag (_boldTag, beginIter, endIter);
					if (style.Underline)
						_textBuffer.ApplyTag (_underlineTag, beginIter, endIter);
					if (style.Italic)
						_textBuffer.ApplyTag (_italicTag, beginIter, endIter);
					if (style.Strikethrough)
						_textBuffer.ApplyTag (_strikeTag, beginIter, endIter);
					
					//TODO: bg color support
					
					if (style.Foreground != ARGBGradient.None)
					{
						tagName = "color_" + ((ARGBColor)style.Foreground).ToHexString (false, false);
						Gtk.TextTag colorTag = _tagTable.Lookup (tagName);
						
						if (colorTag == null)
						{
							colorTag = new Gtk.TextTag (tagName);
							colorTag.ForegroundGdk = new Gdk.Color (((ARGBColor)style.Foreground).R,
							                                        ((ARGBColor)style.Foreground).G,
							                                        ((ARGBColor)style.Foreground).B);
							_tagTable.Add (colorTag);
						}
						
						_textBuffer.ApplyTag (colorTag, beginIter, endIter);
					}
					
					if (!string.IsNullOrEmpty (style.Font))
					{
						tagName = "font_" + style.Font;
						Gtk.TextTag fontTag = _tagTable.Lookup (tagName);
						
						if (fontTag == null)
						{
							fontTag = new Gtk.TextTag (tagName);
							fontTag.Font = style.Font;
							_tagTable.Add (fontTag);
						}
						
						_textBuffer.ApplyTag (fontTag, beginIter, endIter);
					}
					
					if (style.FontSize > 0)
					{
						tagName = "fontsize_" + style.FontSize;
						Gtk.TextTag fontSizeTag = _tagTable.Lookup (tagName);
						
						if (fontSizeTag == null)
						{
							fontSizeTag = new Gtk.TextTag (tagName);
							fontSizeTag.FontDesc.Size = style.FontSize;
							_tagTable.Add (fontSizeTag);
						}
						
						_textBuffer.ApplyTag (fontSizeTag, beginIter, endIter);
					}
				}
			}
			
			TextIter firstIter = _textBuffer.GetIterAtMark (firstMark);
			TextIter lastIter = _textBuffer.EndIter;
			_textBuffer.ApplyTag (_messageTag, firstIter, lastIter);
			_textBuffer.DeleteMark(firstMark);
			
			_textBuffer.SelectRange(_textBuffer.EndIter, _textBuffer.EndIter);
		}
		
		protected virtual void WriteWithAttributes(string text, string[] attributes, bool emoticons)
		{
			// Keep the iteration of where we started.
			TextIter endIter = _textBuffer.EndIter;
			
			if (!emoticons)
			{
				_textBuffer.InsertWithTagsByName(ref endIter, text, attributes);
				return;
			}
			
			string temp = text;
		// CHECKTHIS
		//	string emoticon = String.Empty;
		//	int index = 0;
		//	bool found = false;
			
			/*while(sTemp.Length > 0)
			{
				int iTempIndex = sTemp.Length;
				
				// Go through each emoticon.
				foreach (string sTempEmoticon in EmoticonBasket.EmoticonTable.Keys)
				{
					// If the emoticon exists, keep track of where.
					int iIndexOfEmoticon = sTemp.IndexOf(sTempEmoticon);
					
					if (iIndexOfEmoticon >= 0)
					{
						// Make sure we only keep the lowest index.
						if (iIndexOfEmoticon < iTempIndex)
						{
							iTempIndex = iIndexOfEmoticon;
							sEmoticon = sTempEmoticon;
							bFound = true;
						}
					}
				}
				
				if (bFound)
				{
					string sBefore = sTemp.Substring(iCurrentIndex, iTempIndex);
					
					_textBuffer.InsertWithTagsByName(ref endIter, sBefore, aAttributes);
					
					// Insert the emoticon itself.
					//_textBuffer.InsertPixbuf(ref endIter, EmoticonBasket.Get_Emoticon(sEmoticon).Pixbuf);
					
					sTemp = sTemp.Substring(sBefore.Length+sEmoticon.Length, sTemp.Length - (sBefore.Length+sEmoticon.Length));
					
					bFound = false;
				}
				else
				{
					break;
				}
			}*/
			
			_textBuffer.InsertWithTagsByName(ref endIter, temp, attributes);
			
			_textBuffer.SelectRange(_textBuffer.EndIter, _textBuffer.EndIter);
		}
		
		void AutoScroll ()
		{
			int endY, endHeight;
			TextView.GetLineYrange (_textBuffer.EndIter, out endY, out endHeight);
			
			if (TextView.VisibleRect.Y >= (endY + endHeight - (TextView.VisibleRect.Height * 1.2)))
			{
				TextMark endMark = _textBuffer.CreateMark ("end", _textBuffer.EndIter, false);
				TextView.ScrollToMark (endMark, 0.4, true, 0.0, 1.0);
				_textBuffer.DeleteMark (endMark);
			}
		}
		
		protected void TextViewButtonRelease (object o, ButtonReleaseEventArgs args)
		{
			Gtk.Menu menu = null;
			
			if (_mouseOverIter.ChildAnchor != null)
			{
				IEmoticon emot = null;
				
				foreach (EmoticonMark mark in _emoticons)
				{
					if (mark.Anchor != _mouseOverIter.ChildAnchor)
						continue;
					
					emot = mark.Emoticon;
					break;
				}
				
				if (emot != null)
					menu = MenuUtility.CreateContextMenu ("/Galaxium/Gui/MessageDisplay/Emoticons/Menu", new DefaultExtensionContext (emot));
			}
			else
			{
				//TODO: uncomment + pass correct extension path
				//Menu menu = MenuUtility.CreateContextMenu ("/Galaxium/", new DefaultExtensionContext (this));
			}
			
			if (menu != null)
			{
				menu.Popup (null, null, null, args.Event.Button, args.Event.Time);
				menu.ShowAll ();
			}
		}
		
		[GLib.ConnectBefore]
		void TextViewButtonPress (object sender, Gtk.ButtonPressEventArgs args)
		{
			if ((args.Event.Button == 3) && (_mouseOverIter.ChildAnchor != null)) //disable right click menu
			{
				args.RetVal = false;
				return;
			}
			
			if ((args.Event.Button == 1) && (_activeLink != null))
				PlatformUtility.OpenUrl (_activeLink.URL);
		}
		
		void TextViewMotionNotify (object sender, Gtk.MotionNotifyEventArgs args)
		{
			int x, y;
			TextView.WindowToBufferCoords (TextWindowType.Widget, (int)args.Event.X, (int)args.Event.Y, out x, out y);
			ChangeCursorIfNeeded (x, y);
		}
		
		void TextViewVisibilityNotify (object sender, Gtk.VisibilityNotifyEventArgs args)
		{
			int wx, wy, x, y;
			TextView.GetPointer (out wx, out wy);
			TextView.WindowToBufferCoords (TextWindowType.Widget, wx, wy, out x, out y);
			ChangeCursorIfNeeded (x, y);
		}
		
		protected virtual void ChangeCursorIfNeeded (int x, int y)
		{
			bool mouseOver = false;
			LinkTag linkTag = null;
			
			_mouseOverIter = TextView.GetIterAtLocation (x, y);
			foreach (TextTag tag in _mouseOverIter.Tags)
			{
				if (tag is LinkTag)
				{
					linkTag = (LinkTag)tag;
					mouseOver = true;
					
					break;
				}
			}
			
			if (mouseOver != _mouseOverLink)
			{
				_mouseOverLink = mouseOver;
				
				TextView.GetWindow (TextWindowType.Text).Cursor = mouseOver ? _cursorHand : _cursorPointer;
				_activeLink = mouseOver ? linkTag : null;
			}
		}
	}
}